package jsonmap

import (
	"errors"
	"gitee.com/zhongguo168a/gocodes/datax/schemax/basickind"
	"gitee.com/zhongguo168a/gocodes/datax/schemax/fieldkind"
	"reflect"
	"strconv"
)

func StructToMap(from interface{}, to map[string]interface{}) (err error) {
	if to == nil {
		return errors.New("to is nil")
	}
	if from == nil {
		return
	}
	_, err = structToMapObj(reflect.ValueOf(from), nil, to)
	if err != nil {
		return err
	}

	return
}

func structToMapObj(sourceRef reflect.Value, sourceTyp reflect.Type, itargetMap interface{}) (r interface{}, err error) {
	if sourceRef.Kind() == reflect.Ptr {
		if sourceRef.IsNil() {
			return
		}
		sourceRef = sourceRef.Elem()
		if sourceTyp != nil {
			sourceTyp = sourceTyp.Elem()
		}
	}

	targetMap, ok := itargetMap.(map[string]interface{})
	if ok {
		if targetMap == nil {
			targetMap = map[string]interface{}{}
		}
	} else {
		targetMap = map[string]interface{}{}
	}
	r = targetMap

	rmap := targetMap
	if sourceTyp == nil {
		sourceTyp = sourceRef.Type()
	}
	for i := 0; i < sourceTyp.NumField(); i++ {
		field := sourceTyp.Field(i)
		jsonzTagStr := field.Tag.Get("jsonz")
		if jsonzTagStr == "_" {
			continue
		}
		//tags := parseTag(jsonzTagStr)
		fname := field.Name
		lastName := func() (x string) {
			jsontag := field.Tag.Get("alias")
			x = jsontag
			if x == "" {
				x = fname
			}
			return
		}()

		valRef := sourceRef.Field(i)
		ftyp := field.Type
		catalog := fieldkind.ReflectToFieldKind(ftyp)
		switch catalog {
		case fieldkind.Class:
			if field.Anonymous {
				r, err = structToMapObj(valRef, ftyp, rmap)
				if err != nil {
					err = errors.New(err.Error() + "at class field=" + field.Name)
					return
				}
			} else {
				rmap[lastName], err = structToMapObj(valRef, ftyp, rmap[fname])
				if err != nil {
					err = errors.New(err.Error() + "at class field=" + field.Name)
					return
				}
			}
		case fieldkind.Array:
			rmap[lastName], err = structToMapArray(valRef, ftyp)
			if err != nil {
				err = errors.New(err.Error() + "at class field=" + field.Name)
				return
			}
		case fieldkind.Map:
			rmap[lastName], err = structToMapMap(valRef, nil, ftyp)
			if err != nil {
				err = errors.New(err.Error() + "at class field=" + field.Name)
				return
			}
		case fieldkind.Enum:
			rmap[lastName], err = structToMapEnum(ftyp, valRef)
			if err != nil {
				err = errors.New(err.Error() + "at class field=" + field.Name)
				return
			}
		case fieldkind.Basic:
			rmap[lastName] = structToMapBasic(ftyp, valRef)
		case fieldkind.Any:
			rmap[lastName], err = structToMapAny(ftyp, valRef)
			if err != nil {
				err = errors.New(err.Error() + "at class field=" + field.Name)
				return
			}
		default:
			err = errors.New("not support: " + ftyp.String())
			return

		}
	}
	return
}

func structToMapAny(ftyp reflect.Type, val reflect.Value) (st interface{}, err error) {
	if val.IsNil() {
		return
	}
	catalog := fieldkind.ReflectToFieldKind(val.Elem().Type())
	valref := val.Elem()
	switch catalog {
	case fieldkind.Class:
		st, err = structToMapObj(valref, valref.Type(), nil)
		if err != nil {
			return
		}
	case fieldkind.Basic:
		st = structToMapBasic(ftyp, valref)
	case fieldkind.Map:
		st, err = structToMapMap(valref, nil, valref.Type())
		if err != nil {
			return
		}
	}

	return
}

func structToMapEnum(ftyp reflect.Type, val reflect.Value) (st interface{}, err error) {
	typ, ok := basicTypes[ftyp.Kind()]
	if ok == false {
		err = errors.New("not suppoer enum type, " + ftyp.Key().String())
		return
	}

	st = InterfaceToJSONValue(val.Convert(typ).Interface())
	return
}

func structToMapBasic(basic reflect.Type, val reflect.Value) (r interface{}) {
	kind := basickind.ReflectKindToBasicKind(basic.Kind())
	if kind == basickind.Invalid { // any
		return InterfaceToJSONValue(val.Interface())
	}
	return ConvertBasicToJsonValue(kind, val.Interface())
}

func structToMapArray(sourceArr reflect.Value, ftyp reflect.Type) (rarr []interface{}, err error) {
	if sourceArr.Len() == 0 {
		return
	}

	elemTyp := ftyp.Elem()

	rarr = make([]interface{}, sourceArr.Len(), sourceArr.Cap())
	catalog := fieldkind.ReflectToFieldKind(elemTyp)
	switch catalog {
	case fieldkind.Class:
		for i := 0; i < sourceArr.Len(); i++ {
			imap, classerr := structToMapObj(sourceArr.Index(i), elemTyp, nil)
			if classerr != nil {
				err = errors.New("at index=" + strconv.Itoa(i) + ": " + classerr.Error())
				return
			}
			rarr[i] = imap
		}
	case fieldkind.Basic:
		for i := 0; i < sourceArr.Len(); i++ {
			rarr[i] = structToMapBasic(elemTyp, sourceArr.Index(i))
		}
	case fieldkind.Any:
		for i := 0; i < sourceArr.Len(); i++ {
			rarr[i], err = structToMapAny(elemTyp, sourceArr.Index(i))
			if err != nil {
				err = errors.New("at index=" + strconv.Itoa(i) + ": " + err.Error())
				return
			}
		}
	default:
		err = errors.New("not support: " + elemTyp.String())
		return
	}
	return
}

func structToMapMap(sourceRef reflect.Value, mmap map[string]interface{}, ftyp reflect.Type) (rval interface{}, err error) {
	if sourceRef.IsNil() {
		return nil, nil
	}
	mmap = map[string]interface{}{}
	rval = mmap

	keytyp, ok := basicTypes[ftyp.Key().Kind()]
	if ok == false {
		err = errors.New("not suppoer map key, " + ftyp.Key().String())
		return
	}
	vtyp := ftyp.Elem()
	for _, ckey := range sourceRef.MapKeys() {
		key := InterfaceToJSONKey(ckey.Convert(keytyp).Interface())
		valRef := sourceRef.MapIndex(ckey)
		//vtyp := valRef.Type()
		catalog := fieldkind.ReflectToFieldKind(vtyp)
		switch catalog {
		case fieldkind.Class:
			mmap[key], err = structToMapObj(valRef, vtyp, ckey.String())
			if err != nil {
				err = errors.New(err.Error() + "at map key=" + key)
				return
			}
		case fieldkind.Enum:
			mmap[key], err = structToMapEnum(vtyp, valRef)
			if err != nil {
				err = errors.New(err.Error() + "at map key=" + key)
				return
			}
		case fieldkind.Basic:
			mmap[key] = structToMapBasic(vtyp, valRef)
		case fieldkind.Any:
			mmap[key] = InterfaceToJSONValue(valRef.Interface())
		default:
			err = errors.New("not support: " + vtyp.String())
			return
		}
	}

	return
}

var basicTypes = map[reflect.Kind]reflect.Type{
	reflect.Int:     reflect.TypeOf(0),
	reflect.Int8:    reflect.TypeOf(int8(0)),
	reflect.Int16:   reflect.TypeOf(int16(0)),
	reflect.Int32:   reflect.TypeOf(int32(0)),
	reflect.Int64:   reflect.TypeOf(int64(0)),
	reflect.Uint:    reflect.TypeOf(uint(0)),
	reflect.Uint8:   reflect.TypeOf(uint8(0)),
	reflect.Uint16:  reflect.TypeOf(uint16(0)),
	reflect.Uint32:  reflect.TypeOf(uint32(0)),
	reflect.Uint64:  reflect.TypeOf(uint64(0)),
	reflect.Float32: reflect.TypeOf(float32(0)),
	reflect.Float64: reflect.TypeOf(float64(0)),
	reflect.String:  reflect.TypeOf(""),
	reflect.Bool:    reflect.TypeOf(false),
}
